﻿using System;
using System.Linq;
using Microsoft.SqlServer.Dts.Pipeline;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
using Microsoft.SqlServer.Dts.Runtime;
using Microsoft.SqlServer.Dts.Runtime.Wrapper;
using Microsoft.SqlServer.Types;
using System.Data;
using System.IO;
using System.Collections;
using System.Data.SqlTypes;
using System.Text;

namespace SSISComponents
{
    [DtsPipelineComponent(
        DisplayName = "ShapeFileSource",
        Description = "Import ESRI ShapeFiles",
        IconResource = "SSISComponents.ShapeFileSource.ico",
        UITypeName = "SSISComponents.ShapeFileSourceUI, SSISComponents.Dts.Pipeline.ShapeFileSource, Version=1.1.0.0, Culture=neutral, PublicKeyToken=9b0a5b72a437255d",
        ComponentType = ComponentType.SourceAdapter)]
    public class ShapeFileSource : Microsoft.SqlServer.Dts.Pipeline.PipelineComponent
    {

        #region Members

            private ColumnCollection _Columns = new ColumnCollection();
            private IDTSCustomProperty100 IDTS_ShapeFilePath;
            private IDTSCustomProperty100 IDTS_ShapeFileData;
            private IDTSCustomProperty100 IDTS_IgnoreError;
            private IDTSCustomProperty100 IDTS_SRID;
            private IDTSCustomProperty100 IDTS_SpatialDataType;
            private ArrayList m_columnInfo;
            private ShapeFileReadInfo info;
            private int _SRID = 4326;
            private int _ErrCounter = 0;
            private int _rows = 0;
            private bool bCancel;

            /// <summary>
            /// Struct that is populated with the index of each output column in the buffer,
            /// and the name of the column at the external data source.
            /// </summary>
            private struct ColumnInfo
            {
                public int BufferColumnIndex;
                public string ColumnName;
            }

        #endregion

        #region Design Time

        /// <summary>
        /// ComponentProperties
        /// </summary>
        public override void ProvideComponentProperties()
        {
            base.ProvideComponentProperties();
            RemoveAllInputsOutputsAndCustomProperties();

            //ComponentMetaData.Name = "ShapeFileSource";

            // add an output
            ComponentMetaData.OutputCollection.New();

            // Get the assembly version and set that as our current version.
            SetComponentVersion();

            IDTS_ShapeFilePath = ComponentMetaData.CustomPropertyCollection.New();
            IDTS_ShapeFilePath.Name = "ShapeFilePath";
            IDTS_ShapeFilePath.Description = "Full path to the ESRI Shapefile used as input.";
            IDTS_ShapeFilePath.UITypeEditor = "System.Windows.Forms.Design.FileNameEditor, System.Design, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a";
            IDTS_ShapeFilePath.ExpressionType = DTSCustomPropertyExpressionType.CPET_NOTIFY;

            IDTS_ShapeFileData = ComponentMetaData.CustomPropertyCollection.New();
            IDTS_ShapeFileData.Name = "ShapeFileData";
            IDTS_ShapeFileData.Description = "ShapeFile Columns";
            IDTS_ShapeFileData.ExpressionType = DTSCustomPropertyExpressionType.CPET_NONE;

            IDTS_IgnoreError = ComponentMetaData.CustomPropertyCollection.New();
            IDTS_IgnoreError.Name = "IgnoreConversionError";
            IDTS_IgnoreError.Description = "Ignores conversion errors on importing data";
            IDTS_IgnoreError.ExpressionType = DTSCustomPropertyExpressionType.CPET_NOTIFY;

            IDTS_SRID = ComponentMetaData.CustomPropertyCollection.New();
            IDTS_SRID.Name = "SRID";
            IDTS_SRID.Description = "A Spatial Reference System Identifier (SRID) is a unique value used to unambiguously identify projected, unprojected, and local spatial coordinate system definitions.";
            IDTS_SRID.ExpressionType = DTSCustomPropertyExpressionType.CPET_NOTIFY;

            IDTS_SpatialDataType = ComponentMetaData.CustomPropertyCollection.New();
            IDTS_SpatialDataType.Name = "SpatialDataType";
            IDTS_SpatialDataType.Description = "The geometry data type supports planar, or Euclidean (flat-earth), data. In addition, SQL Server supports the geography data type, which stores ellipsoidal (round-earth) data, such as GPS latitude and longitude coordinates.";
            IDTS_SpatialDataType.ExpressionType = DTSCustomPropertyExpressionType.CPET_NOTIFY;
            //IDTS_SpatialDataType.Value = "Geometry";
            
            // name the output
            ComponentMetaData.OutputCollection[0].Name = "ShapeFileSource adapter output";
            ComponentMetaData.OutputCollection[0].ExternalMetadataColumnCollection.IsUsed = true;

            // Set we want to validate external metadata
            ComponentMetaData.ValidateExternalMetadata = true;
            
        }

        /// <summary>
        /// Generates new output columns
        /// </summary>
        public override void ReinitializeMetaData()
        {

            IDTSOutput100 output = ComponentMetaData.OutputCollection[0];

            output.OutputColumnCollection.RemoveAll();
            output.ExternalMetadataColumnCollection.RemoveAll();

            // Generate the columns with metadata
            GenerateColumns();

            foreach (Column dataColumn in _Columns)
            {

                // create a new column
                IDTSOutputColumn100 outputcolNew = output.OutputColumnCollection.New();

                if (string.IsNullOrEmpty(dataColumn.Name))
                {
                    throw new PipelineComponentHResultException(HResults.DTS_E_DATASOURCECOLUMNWITHNONAMEFOUND);
                }

                outputcolNew.Name = dataColumn.Name;
                outputcolNew.SetDataTypeProperties(dataColumn.DataType, dataColumn.Length, dataColumn.Precision, dataColumn.Scale, dataColumn.Codepage);
                
                /// Description for SqlGeometry data is set to "GEOMETRY".
                /// Perhaps it is possible to develop a componet that reads
                /// this automatically.
                if (dataColumn.Name.ToUpper() != "POINTS")
                {
                    outputcolNew.Description = dataColumn.Description;
                }
                else
                {
                    outputcolNew.Description = "GEOMETRY";
                }

                CreateExternalMetaDataColumn(output.ExternalMetadataColumnCollection, outputcolNew);
            }

            // Exclusion Group
            output.ExclusionGroup = 0;
            // Synchronous Input
            output.SynchronousInputID = 0;

            base.ReinitializeMetaData();

        }

        /// <summary>
        /// Validates input data and component properties
        /// </summary>
        /// <returns></returns>
        public override DTSValidationStatus Validate()
        {
            IDTSOutput100 output = ComponentMetaData.OutputCollection[0];

            
            if (ComponentMetaData.InputCollection.Count != 0)
            {
                ComponentMetaData.FireError(0, ComponentMetaData.Name, "Has an input when no input should exist.", "", 0, out bCancel);
                return DTSValidationStatus.VS_ISCORRUPT;
            }

            // check for the ShapeFileData
            try
            {
                if (ComponentMetaData.CustomPropertyCollection["ShapeFileData"].Value.ToString() == null)
                {
                    ComponentMetaData.FireError(0, ComponentMetaData.Name, "No ShapeFile Data specified.", "", 0, out bCancel);
                    return DTSValidationStatus.VS_ISBROKEN;
                }
            }
            catch
            {
                ComponentMetaData.FireError(0, ComponentMetaData.Name, "No ShapeFile Data specified.", "", 0, out bCancel);
                return DTSValidationStatus.VS_ISBROKEN;
            }

            //check for the ShapeFilePath
            try
            {
                if (ComponentMetaData.CustomPropertyCollection["ShapeFilePath"].Value.ToString() == null)
                {
                    ComponentMetaData.FireError(0, ComponentMetaData.Name, "No ShapeFile specified.", "", 0, out bCancel);
                    return DTSValidationStatus.VS_ISBROKEN;
                }
            }
            catch
            {
                ComponentMetaData.FireError(0, ComponentMetaData.Name, "No ShapeFile specified.", "", 0, out bCancel);
                return DTSValidationStatus.VS_ISBROKEN;
            }

            //Check for the ShapeFile (System.IO.File.Exist)
            try
            {
                if (!System.IO.File.Exists(ComponentMetaData.CustomPropertyCollection["ShapeFilePath"].Value.ToString()))
                {
                    ComponentMetaData.FireError(0, ComponentMetaData.Name, "ShapeFile not exist.", "", 0, out bCancel);
                    return DTSValidationStatus.VS_ISBROKEN;
                }
            }
            catch
            {
                ComponentMetaData.FireError(0, ComponentMetaData.Name, "ShapeFile not exist.", "", 0, out bCancel);
                return DTSValidationStatus.VS_ISBROKEN;
            }

            // Validate the output columns against the external metadata?
            if (ComponentMetaData.ValidateExternalMetadata)
            {
                // Does the output column collection match the columns at the data source?
                if (!this.AreOutputColumnsValid())
                {
                    // No, post a warning, and fix the errors in reinitializemetadata.                    
                    //return DTSValidationStatus.VS_NEEDSNEWMETADATA;
                    //ComponentMetaData.FireWarning(0, ComponentMetaData.Name, "The output columns do not match the external data source.", "", 0);
                    ReinitializeMetaData();
                }

                if (!ComponentValidation.DoesExternalMetaDataMatchOutputMetaData(output))
                {
                    ComponentMetaData.FireWarning(0, ComponentMetaData.Name, "The ExternalMetaDataColumns do not match the output columns.", "", 0);
                    return DTSValidationStatus.VS_NEEDSNEWMETADATA;
                }
            }
            else
            {
                if (!ComponentValidation.DoesOutputColumnMetaDataMatchExternalColumnMetaData(ComponentMetaData.OutputCollection[0]))
                {
                    // No, post a warning, and fix the errors in ReintializeMetaData.                    
                    ComponentMetaData.FireWarning(0, ComponentMetaData.Name, "Output columns do not match external metadata.", "", 0);
                    return DTSValidationStatus.VS_NEEDSNEWMETADATA;
                }

            }

            // Return base class validation result.
            return base.Validate();

        }

        //=================================================================================================

        #region Overloaded insert and delete methods ( insert/delete inputs, outputs and columns )

        public override IDTSInput100 InsertInput(DTSInsertPlacement insertPlacement, int inputID)
        {
            throw new PipelineComponentHResultException("You cannot insert an input", HResults.DTS_W_GENERICWARNING);
        }

        public override IDTSOutput100 InsertOutput(DTSInsertPlacement insertPlacement, int outputID)
        {
            throw new PipelineComponentHResultException("You cannot insert an output", HResults.DTS_W_GENERICWARNING);
        }

        public override void DeleteInput(int inputID)
        {
            throw new PipelineComponentHResultException("You cannot delete an input", HResults.DTS_W_GENERICWARNING);
        }

        public override void DeleteOutput(int outputID)
        {
            throw new PipelineComponentHResultException("You cannot delete an ouput", HResults.DTS_W_GENERICWARNING);
        }

        public override void DeleteExternalMetadataColumn(int iID, int iExternalMetadataColumnID)
        {
            throw new PipelineComponentHResultException("You cannot delete external metadata column", HResults.DTS_W_GENERICWARNING);
        }

        public override IDTSOutputColumn100 InsertOutputColumnAt(int outputID, int outputColumnIndex, string name, string description)
        {
            throw new PipelineComponentHResultException("You cannot add output column", HResults.DTS_W_GENERICWARNING);
        }

        public override IDTSExternalMetadataColumn100 InsertExternalMetadataColumnAt(int iID, int iExternalMetadataColumnIndex, string strName, string strDescription)
        {
            throw new PipelineComponentHResultException("You cannot add external metadata column", HResults.DTS_W_GENERICWARNING);
        }

        //=================================================================================================

        #endregion

        //=================================================================================================

        #endregion

        #region Runtime

        /// <summary>
        /// 
        /// </summary>
        public override void PreExecute()
        {
            m_columnInfo = new ArrayList();
            IDTSOutput100 output = ComponentMetaData.OutputCollection[0];

            foreach (IDTSOutputColumn100 col in output.OutputColumnCollection)
            {
                ColumnInfo ci = new ColumnInfo();
                ci.BufferColumnIndex = BufferManager.FindColumnByLineageID(output.Buffer, col.LineageID);
                ci.ColumnName = col.Name;
                m_columnInfo.Add(ci);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="outputs"></param>
        /// <param name="outputIDs"></param>
        /// <param name="buffers"></param>
        public override void PrimeOutput(int outputs, int[] outputIDs, PipelineBuffer[] buffers)
        {
            PipelineBuffer buffer = buffers[0];
            bool bout = true;
            try
            {
                string strFilename = ComponentMetaData.CustomPropertyCollection["ShapeFilePath"].Value.ToString();
                
                info = new ShapeFileReadInfo();
                info.FileName = strFilename;
                DataTable AttributeColumns = info.AttributeColumns();
                info.ShapeFile = new ShapeFile();
                info.Stream = new FileStream(info.FileName, FileMode.Open, FileAccess.Read);
                info.ShapeFile.ReadShapeFileHeader(info.Stream);

                WriteShapeFileInfo(info, bout, AttributeColumns.Columns.Count.ToString());

                info.ShapeFile.Read(info.FileName);

                foreach (ShapeFileRecord record in info.ShapeFile.Records)
                {
                    buffer.AddRow();
                    _rows += 1;
                    int colCount = 0;
                    foreach (Column col in _Columns)
                    {
                        switch (col.Name)
                        {
                            case "ContentLength":
                                buffer.SetInt32(colCount, record.ContentLength);
                                break;
                            case "NumberOfParts":
                                buffers[0].SetInt32(colCount, record.NumberOfParts);
                                break;
                            case "NumberOfPoints":
                                buffers[0].SetInt32(colCount, record.NumberOfPoints);
                                break;
                            case "Points":
                                if (ComponentMetaData.CustomPropertyCollection["SpatialDataType"].Value.ToString() != "GEOGRAPHY")
                                {
                                    buffers[0].AddBlobData(colCount, geom(record).STAsBinary().Buffer);
                                }
                                else 
                                {
                                    buffers[0].AddBlobData(colCount, geog(record).STAsBinary().Buffer);
                                }

                                
                                break;
                            case "RecordNumber":
                                buffers[0].SetInt32(colCount, record.RecordNumber);
                                break;
                            case "ShapeType":
                                buffers[0].SetInt32(colCount, record.ShapeType);
                                break;
                            case "XMax":
                                buffers[0].SetDouble(colCount, record.XMax);
                                break;
                            case "XMin":
                                buffers[0].SetDouble(colCount, record.XMin);
                                break;
                            case "YMax":
                                buffers[0].SetDouble(colCount, record.YMax);
                                break;
                            case "YMin":
                                buffers[0].SetDouble(colCount, record.YMin);
                                break;
                            default:
                                buffers[0].SetString(colCount, record.Attributes[col.Name].ToString());
                                break;
                        }
                        colCount += 1;
                    }
                    /// Write status info each 1000 rows
                    if (_rows % 1000 == 0)
                    {
                        ComponentMetaData.FireInformation(0, "SSIS ShapeFileSource", _rows.ToString() + " rows processed", null, 0, ref bout); 
                    }
                }
                ComponentMetaData.FireInformation(0, "SSIS ShapeFileSource", "Component wrote " + _rows.ToString() + " rows.", null, 0, ref bout); 
            }
            catch (Exception ex)
            {
                if (Convert.ToBoolean(ComponentMetaData.CustomPropertyCollection["IgnoreConversionError"].Value) != true)
                {
                    ComponentMetaData.FireError(0, "ShapeFileSource", "An error occured during processing the shape file.", "", 0, out bCancel);
                }
                else
                {
                    ComponentMetaData.FireWarning(0, "ShapeFileSource", "ERROR: " + ex.ToString(), "", 0);
                }
            }
            finally
            {
                buffer.SetEndOfRowset();
            }
        }

        /// <summary>
        /// Called at the end of the execution of a component.
        /// </summary>
        public override void PostExecute()
        {
            base.PostExecute();
            bool bref = true;
            if (_ErrCounter > 0)
            {
                ComponentMetaData.FireWarning(0, "ShapeFileSource", _ErrCounter.ToString() + " Conversion error occurred during the import.", "", 0);
            }
            else
            {
                ComponentMetaData.FireInformation(0, "ShapeFileSource", "0 Conversion error occurred during the import.", "", 0, ref bref);
            }
        }

        #endregion
     
        #region Helpers

        /// <summary>
        /// The geodata of the current record as string
        /// </summary>
        /// <param name="record">Current ShapeFileRecord</param>
        /// <returns></returns>
        public string PointsToGeoString(ShapeFileRecord record)
        {
            int PointCounter = 0;
            int[] _Parts = PartsLength(record);
            StringBuilder[] _GeoParts = new StringBuilder[_Parts.Length - 1];
            StringBuilder _GEO = new StringBuilder();
            int _points = record.NumberOfPoints;

            if (_Parts.Length >= 1 && _points > 1)
            {
                for (int PL = 0; PL < _Parts.Length - 1; PL++)
                {
                    _GeoParts[PL] = new StringBuilder();
                    _GeoParts[PL].Append("(");
                    for (int Points = 0; Points < _Parts[PL]; Points++)
                    {
                        _GeoParts[PL].Append(geopoints(record.Points[PointCounter]));
                        _GeoParts[PL].Append(", ");
                        PointCounter += 1;
                    }
                    _GeoParts[PL].Remove(_GeoParts[PL].Length - 2, 2);
                    _GeoParts[PL].Append(")");
                }
                if (_GeoParts.Length == 1)
                {
                    for (int g = 0; g < _GeoParts.Length; g++)
                    {
                        _GEO.Append(_GeoParts[g]);

                    }
                }
                else
                {
                    _GEO.Append("(");
                    for (int g = 0; g < _GeoParts.Length - 1; g++)
                    {
                        _GEO.Append(_GeoParts[g]);
                        _GEO.Append(", ");

                    }
                    _GEO.Remove(_GEO.Length - 2, 2);
                    _GEO.Append("), (");
                    _GEO.Append(_GeoParts[_GeoParts.Length - 1]);
                    _GEO.Append(")");
                }
            }
            else
            {
                System.Windows.Point po = record.Points[0];
                _GEO.Append("(");
                _GEO.Append(po.X.ToString().Replace(",", "."));
                _GEO.Append(" ");
                _GEO.Append(po.Y.ToString().Replace(",", "."));
                _GEO.Append(")");
            }

            switch (record.ShapeType)
            {
                case 0:
                    _GEO.Append("");
                    break;
                case 1:
                    _GEO.Insert(0, "POINT ");
                    break;
                case 3:
                    _GEO.Insert(0, "LINESTRING ");
                    break;
                case 5:
                    if (_GeoParts.Length == 1)
                    {
                        _GEO.Insert(0, "POLYGON (");
                        _GEO.Append(")");
                    }
                    else
                    {
                        _GEO.Insert(0, "MULTIPOLYGON (");
                        _GEO.Append(")");
                    }
                    break;
                case 8:
                    _GEO.Append("");
                    break;
            }
            return _GEO.ToString();
        }

        private void WriteShapeFileInfo(ShapeFileReadInfo info, Boolean bout, string ColumnCount)
        {
            ComponentMetaData.FireInformation(0, "ShapeFileSource", "ShapeFile: " + info.FileName, "", 0, ref bout);
            ComponentMetaData.FireInformation(0, "ShapeFileSource", "dbaseFile: " + info.dbaseFile, "", 0, ref bout);
            ComponentMetaData.FireInformation(0, "ShapeFileSource", "AttributeColumns: " + ColumnCount, "", 0, ref bout);
            ComponentMetaData.FireInformation(0, "ShapeFileSource", "FileCode: " + info.ShapeFile.FileHeader.FileCode, "", 0, ref bout);
            ComponentMetaData.FireInformation(0, "ShapeFileSource", "FileLength: " + info.ShapeFile.FileHeader.FileLength, "", 0, ref bout);
            ComponentMetaData.FireInformation(0, "ShapeFileSource", "ShapeType: " + info.ShapeFile.FileHeader.ShapeType, "", 0, ref bout);
            ComponentMetaData.FireInformation(0, "ShapeFileSource", "Version: " + info.ShapeFile.FileHeader.Version, "", 0, ref bout);
            ComponentMetaData.FireInformation(0, "ShapeFileSource", "XMax: " + info.ShapeFile.FileHeader.XMax, "", 0, ref bout);
            ComponentMetaData.FireInformation(0, "ShapeFileSource", "XMin: " + info.ShapeFile.FileHeader.XMin, "", 0, ref bout);
            ComponentMetaData.FireInformation(0, "ShapeFileSource", "YMax: " + info.ShapeFile.FileHeader.YMax, "", 0, ref bout);
            ComponentMetaData.FireInformation(0, "ShapeFileSource", "YMin: " + info.ShapeFile.FileHeader.YMin, "", 0, ref bout);
            ComponentMetaData.FireInformation(0, "ShapeFileSource", "SRID: " + ComponentMetaData.CustomPropertyCollection["SRID"].Value.ToString(), "", 0, ref bout);
            ComponentMetaData.FireInformation(0, "ShapeFileSource", "SpatialDataType: " + ComponentMetaData.CustomPropertyCollection["SpatialDataType"].Value.ToString(), "", 0, ref bout);           
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="po"></param>
        /// <returns></returns>
        private string geopoints(System.Windows.Point po)
        {
            return (po.X.ToString()).Replace(",", ".") + " " + (po.Y.ToString()).Replace(",", ".");
        }

        /// <summary>
        /// An integer array which contains the length of each part in a ring
        /// </summary>
        /// <param name="record">Current ShapeFileRecord</param>
        /// <returns></returns>
        private int[] PartsLength(ShapeFileRecord record)
        {
            record.Parts.Add(record.NumberOfPoints);
            int[] _Parts = new int[record.Parts.Count];
            if (_Parts.Length > 0)
            {
                for (int ip = 0; ip < record.Parts.Count - 1; ip++)
                {
                    _Parts[ip] = record.Parts[ip + 1] - record.Parts[ip];
                }
                _Parts.Take(_Parts.Length - 2);
            }
            record.Parts.RemoveAt(record.Parts.Count - 1);
            return _Parts;
        }

        /// <summary>
        /// The SqlGeometry Data of the current record
        /// </summary>
        /// <param name="record">Current ShapeFileRecord</param>
        /// <returns></returns>
        public SqlGeometry geom(ShapeFileRecord record)
        {
            string data = PointsToGeoString(record);
            SqlGeometry GeoPoint = null;

            try
            {
                _SRID = 0;
                GeoPoint = SqlGeometry.STGeomFromText(new SqlChars(data), _SRID);
                GeoPoint = GeoPoint.MakeValid();               
            }
            catch
            {
                ComponentMetaData.FireWarning(0, "ShapeFileSource", "The specified input does not represent a valid geometry instance.", "", 0);
                GeoPoint = new SqlGeometry();
                _ErrCounter += 1;
            }
            return GeoPoint;
        }


        /// <summary>
        /// The SqlGeometry Data of the current record
        /// </summary>
        /// <param name="record">Current ShapeFileRecord</param>
        /// <returns></returns>
        public SqlGeography geog(ShapeFileRecord record)
        {
            string data = PointsToGeoString(record);
            SqlGeography GeoPoint = null;

            try
            {
                _SRID = Convert.ToInt32(ComponentMetaData.CustomPropertyCollection["SRID"].Value.ToString());
                GeoPoint = SqlGeography.STGeomFromText(new SqlChars(data), _SRID);
            }
            catch
            {
                ComponentMetaData.FireWarning(0, "ShapeFileSource", "The specified input does not represent a valid geography instance.", "", 0);
                GeoPoint = new SqlGeography();
                _ErrCounter += 1;
            }
            return GeoPoint;
        }

        /// <summary>
        /// Create an external metadata column for each output. Map the two 
        /// by setting the ExternalMetaDataColumnID property of the output to 
        /// the external metadata column.
        /// </summary>
        /// <param name="output">The output the columns are added to.</param>
        private static void CreateExternalMetaDataColumn(IDTSExternalMetadataColumnCollection100 externalCollection, IDTSOutputColumn100 column)
        {
            // For each output column create an external meta data columns.
            IDTSExternalMetadataColumn100 eColumn = externalCollection.New();
            eColumn.Name = column.Name;
            eColumn.DataType = column.DataType;
            eColumn.Precision = column.Precision;
            eColumn.Length = column.Length;
            eColumn.Scale = column.Scale;

            // wire the output column to the external metadata
            column.ExternalMetadataColumnID = eColumn.ID;
        }

        /// <summary>
        /// Generate ColumnCollection for External Metadata, 
        /// Custom Properties and some more
        /// </summary>
        private void GenerateColumns()
        {
            try
            {
                if (ComponentMetaData.CustomPropertyCollection["ShapeFileData"].Value.ToString() != null)
                {
                    _Columns.Clear();
                    DataSet ds = new DataSet();
                    ds.ReadXml(new StringReader(ComponentMetaData.CustomPropertyCollection["ShapeFileData"].Value.ToString()));

                    foreach (DataTable dt in ds.Tables)
                    {
                        foreach (DataRow row in dt.Rows)
                        {
                            if (_Columns.IndexOf(row[0].ToString()) == -1)
                            {
                                int Length = Convert.ToInt32(row[2].ToString());
                                int Precision = Convert.ToInt32(row[3].ToString());
                                int Scale = Convert.ToInt32(row[4].ToString());
                                int Codepage = Convert.ToInt32(row[5].ToString());
                                _Columns.Add(row[0].ToString(), GetDataType(row[1].ToString()), Length, Precision, Scale, Codepage);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                ComponentMetaData.FireError(HResults.DTS_E_VALIDATIONFAILED, ComponentMetaData.Name, ex.ToString(), "", 0, out bCancel);
            }
        }

        /// <summary>
        /// Checks if each output column is valid
        /// </summary>
        /// <returns></returns>
        private bool AreOutputColumnsValid()
        {
            IDTSOutput100 output = ComponentMetaData.OutputCollection[0];
            GenerateColumns();

            // Output columns should be subset of external columns
            if (output.OutputColumnCollection.Count != _Columns.Count)
            {
                return false;
            }

            // Otherwise, walk the WQL columns and validate each column
            for (int x = 0; x < output.OutputColumnCollection.Count; x++)
            {
                IDTSOutputColumn100 column = output.OutputColumnCollection[x];

                Column Col = _Columns.GetColumnByName(column.Name);

                // Does the column exist, by name, in the WQL schema?
                if (Col == null || !ColumnMatchesOutputColumn(Col, column))
                {
                    // Fire a warning before returning
                    ComponentMetaData.FireWarning(0, ComponentMetaData.Name, "The output column " + column.IdentificationString + " does not match the data source.", "", 0);
                    return false;
                }
            }

            // If this code is reached, all of the columns are valid.
            return true;
        }

        /// <summary>
        /// Checks if each internal column match to the corresponding output column
        /// </summary>
        /// <param name="Col"></param>
        /// <param name="column"></param>
        /// <returns></returns>
        private bool ColumnMatchesOutputColumn(Column Col, IDTSOutputColumn100 column)
        {
            DataType dt = Col.DataType;
            int length = Col.Length;
            int scale = Col.Scale;
            int codePage = Col.Codepage;

            if (dt == column.DataType && length == column.Length && scale == column.Scale && codePage == column.CodePage)
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// Gets the ssis datatype
        /// </summary>
        /// <param name="DT"></param>
        /// <returns></returns>
        private DataType GetDataType(string DT)
        {
            switch (DT)
            {
                case "DT_BOOL": return DataType.DT_BOOL;
                case "DT_BYTES": return DataType.DT_BYTES;
                case "DT_CY": return DataType.DT_CY;
                case "DT_DATE": return DataType.DT_DATE;
                case "DT_DBDATE": return DataType.DT_DBDATE;
                case "DT_DBTIME": return DataType.DT_DBTIME;
                case "DT_DBTIME2": return DataType.DT_DBTIME2;
                case "DT_DBTIMESTAMP": return DataType.DT_DBTIMESTAMP;
                case "DT_DBTIMESTAMP2": return DataType.DT_DBTIMESTAMP2;
                case "DT_DBTIMESTAMPOFFSET": return DataType.DT_DBTIMESTAMPOFFSET;
                case "DT_DECIMAL": return DataType.DT_DECIMAL;
                case "DT_EMPTY": return DataType.DT_EMPTY;
                case "DT_FILETIME": return DataType.DT_FILETIME;
                case "DT_GUID": return DataType.DT_GUID;
                case "DT_I1": return DataType.DT_I1;
                case "DT_I2": return DataType.DT_I2;
                case "DT_I4": return DataType.DT_I4;
                case "DT_I8": return DataType.DT_I8;
                case "DT_IMAGE": return DataType.DT_IMAGE;
                case "DT_NTEXT": return DataType.DT_NTEXT;
                case "DT_NULL": return DataType.DT_NULL;
                case "DT_NUMERIC": return DataType.DT_NUMERIC;
                case "DT_R4": return DataType.DT_R4;
                case "DT_R8": return DataType.DT_R8;
                case "DT_STR": return DataType.DT_STR;
                case "DT_TEXT": return DataType.DT_TEXT;
                case "DT_UI1": return DataType.DT_UI1;
                case "DT_UI2": return DataType.DT_UI2;
                case "DT_UI4": return DataType.DT_UI4;
                case "DT_UI8": return DataType.DT_UI8;
                case "DT_WSTR": return DataType.DT_WSTR;
                default: return DataType.DT_WSTR;
            }
        }

        /// <summary>
        /// Set the componet version to match the assembly version.
        /// </summary>
        public void SetComponentVersion()
        {
            // Get the assembly version and set that as our current version.
            DtsPipelineComponentAttribute attr = (DtsPipelineComponentAttribute)
                    Attribute.GetCustomAttribute(this.GetType(), typeof(DtsPipelineComponentAttribute), false);
            System.Diagnostics.Trace.Assert(attr != null, "Could not get attributes");
            ComponentMetaData.Version = attr.CurrentVersion;
        }

        #endregion

    }

}
